Package de.yaams.extensions.basemap.tiled.mapeditor

Source Code of de.yaams.extensions.basemap.tiled.mapeditor.MapEditor

/*
* Tiled Map Editor, (c) 2004-2008
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* Adam Turk <aturk@biggeruniverse.com> Bjorn Lindeijer <bjorn@lindeijer.nl>
*/

package de.yaams.extensions.basemap.tiled.mapeditor;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Area;
import java.io.IOException;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Stack;
import java.util.Vector;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.undo.UndoableEditSupport;

import de.yaams.extensions.basemap.tiled.core.Map;
import de.yaams.extensions.basemap.tiled.core.MapChangeListener;
import de.yaams.extensions.basemap.tiled.core.MapChangedEvent;
import de.yaams.extensions.basemap.tiled.core.MapLayer;
import de.yaams.extensions.basemap.tiled.core.MapObject;
import de.yaams.extensions.basemap.tiled.core.ObjectGroup;
import de.yaams.extensions.basemap.tiled.core.Tile;
import de.yaams.extensions.basemap.tiled.core.TileLayer;
import de.yaams.extensions.basemap.tiled.core.TileSet;
import de.yaams.extensions.basemap.tiled.io.MapHelper;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.AddLayerAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.CloneLayerAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.DeleteLayerAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.ExitAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.MergeAllLayersAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.MergeLayerDownAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.MoveLayerDownAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.MoveLayerUpAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.OpenRecentAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.SaveAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.SaveAsAction;
import de.yaams.extensions.basemap.tiled.mapeditor.actions.SaveAsImageAction;
import de.yaams.extensions.basemap.tiled.mapeditor.brush.AbstractBrush;
import de.yaams.extensions.basemap.tiled.mapeditor.brush.Brush;
import de.yaams.extensions.basemap.tiled.mapeditor.brush.CustomBrush;
import de.yaams.extensions.basemap.tiled.mapeditor.brush.ShapeBrush;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.AboutDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.BrushDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.ConfigurationDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.NewTilesetDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.ObjectDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.PropertiesDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.ResizeDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.SearchDialog;
import de.yaams.extensions.basemap.tiled.mapeditor.dialogs.TilesetManager;
import de.yaams.extensions.basemap.tiled.mapeditor.selection.SelectionLayer;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.AddObjectEdit;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.MapLayerEdit;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.MapLayerStateEdit;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.MoveLayerEdit;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.MoveObjectEdit;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.RemoveObjectEdit;
import de.yaams.extensions.basemap.tiled.mapeditor.undo.UndoHandler;
import de.yaams.extensions.basemap.tiled.mapeditor.util.LayerTableModel;
import de.yaams.extensions.basemap.tiled.mapeditor.util.MapEventAdapter;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.BrushPreview;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.FloatablePanel;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.MiniMapViewer;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.TButton;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.TMenuItem;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.TabbedTilesetsPane;
import de.yaams.extensions.basemap.tiled.mapeditor.widget.TilePalettePanel;
import de.yaams.extensions.basemap.tiled.util.TiledConfiguration;
import de.yaams.extensions.basemap.tiled.view.MapView;
import de.yaams.maker.helper.gui.YEx;
import de.yaams.maker.helper.gui.YFactory;
import de.yaams.maker.helper.gui.YToolBar;
import de.yaams.maker.programm.YaFrame;
import de.yaams.maker.programm.project.Project;
import de.yaams.maker.programm.project.tab.ProjectTab;

public abstract class MapEditor extends ProjectTab implements ActionListener, MouseListener, MouseMotionListener, MapChangeListener,
    ListSelectionListener, ChangeListener, ComponentListener {
  /**
   *
   */
  private static final long serialVersionUID = -3848283432802307224L;
  // Constants and the like
  private static final int PS_POINT = 0;
  private static final int PS_PAINT = 1;
  private static final int PS_ERASE = 2;
  private static final int PS_POUR = 3;
  private static final int PS_EYED = 4;
  private static final int PS_MARQUEE = 5;
  private static final int PS_MOVE = 6;
  private static final int PS_ADDOBJ = 7;
  private static final int PS_REMOVEOBJ = 8;
  private static final int PS_MOVEOBJ = 9;

  private Cursor curDefault;
  private Cursor curEyed;

  /** Current release version. */
  public static final String version = "0.7.2";

  private Map currentMap;
  private MapView mapView;
  private final UndoHandler undoHandler;
  private final UndoableEditSupport undoSupport;
  private final MapEventAdapter mapEventAdapter;
  private static final Preferences prefs = TiledConfiguration.root();

  private int currentPointerState;
  private Tile currentTile;
  private MapObject currentObject = null;
  private int currentLayer = -1;
  private boolean bMouseIsDown, bMouseIsDragging;
  private SelectionLayer cursorHighlight;
  private Point mousePressLocation, mouseInitialPressLocation;
  private Point mouseLastPixelLocation;
  private Point mouseInitialScreenLocation;
  private Point moveDist;
  private int mouseButton;
  private AbstractBrush currentBrush;
  private SelectionLayer marqueeSelection;
  private MapLayer clipboardLayer;
  private float relativeMidX, relativeMidY;

  // GUI components
  private JPanel mainPanel;
  private JPanel dataPanel;
  private JPanel statusBar;
  private JMenuBar menuBar;
  private JCheckBoxMenuItem gridMenuItem, boundaryMenuItem, cursorMenuItem;
  private JCheckBoxMenuItem coordinatesMenuItem;
  private JMenu recentMenu;
  private JScrollPane mapScrollPane;
  private JTable layerTable;
  // private JList editHistoryList;
  private MiniMapViewer miniMap;

  private BrushPreview brushPreview;
  private JPanel appFrame;
  private JSlider opacitySlider;
  private JLabel zoomLabel, tileCoordsLabel;

  private AbstractButton paintButton, eraseButton, pourButton;
  private AbstractButton eyedButton, marqueeButton, moveButton;
  private AbstractButton objectMoveButton, objectAddButton;
  private AbstractButton objectRemoveButton;

  private TabbedTilesetsPane tabbedTilesetsPane;
  private AboutDialog aboutDialog;
  private MapLayerEdit paintEdit;

  private FloatablePanel layersPanel;
  private FloatablePanel tilesetsPanel;

  // private TileInstancePropertiesDialog tileInstancePropertiesDialog;
  // private JButton tileInstancePropertiesButton;

  /** Available brushes */
  private Vector<?> brushes = new Vector<Object>();
  private Brush eraserBrush;

  // Actions
  private final SaveAction saveAction;
  private final SaveAsAction saveAsAction;
  private final SaveAsImageAction saveAsImageAction;
  private final Action exitAction;
  private final Action zoomInAction, zoomOutAction, zoomNormalAction;
  private final Action rot90Action, rot180Action, rot270Action;
  private final Action flipHorAction, flipVerAction;
  private final Action selectAllAction, inverseAction, cancelSelectionAction;
  private final Action addLayerAction, cloneLayerAction, deleteLayerAction;
  private final Action moveLayerDownAction, moveLayerUpAction;
  private final Action mergeLayerDownAction, mergeAllLayersAction;
  // private final Action addObjectGroupAction;

  private static final String IMPORT_ERROR_MSG = Resources.getString("dialog.newtileset.import.error.message");

  private static final String PANEL_TILE_PALETTE = Resources.getString("panel.tilepalette.title");
  private static final String PANEL_LAYERS = Resources.getString("panel.layers.title");

  private static final String TOOL_PAINT = Resources.getString("tool.paint.name");
  private static final String TOOL_ERASE = Resources.getString("tool.erase.name");
  private static final String TOOL_FILL = Resources.getString("tool.fill.name");
  private static final String TOOL_EYE_DROPPER = Resources.getString("tool.eyedropper.name");
  private static final String TOOL_SELECT = Resources.getString("tool.select.name");
  private static final String TOOL_MOVE_LAYER = Resources.getString("tool.movelayer.name");
  private static final String TOOL_ADD_OBJECT = Resources.getString("tool.addobject.name");
  private static final String TOOL_REMOVE_OBJECT = Resources.getString("tool.removeobject.name");
  private static final String TOOL_MOVE_OBJECT = Resources.getString("tool.moveobject.name");

  public MapEditor(Project p) {
    super(p);
    /*
     * eraserBrush = new Eraser(); brushes.add(eraserBrush());
     * setBrush(eraserBrush);
     */

    /*
     * try { Image imgPaintCursor = Resources.getImage("cursor-pencil.png");
     *
     * curPaint = Toolkit.getDefaultToolkit().createCustomCursor(
     * imgPaintCursor, new Point(0,0), "paint"); } catch (Exception e) {
     * e.printStackTrace(); }
     */

    curEyed = new Cursor(Cursor.CROSSHAIR_CURSOR);
    curDefault = new Cursor(Cursor.DEFAULT_CURSOR);

    undoHandler = new UndoHandler(this);
    undoSupport = new UndoableEditSupport();
    undoSupport.addUndoableEditListener(undoHandler);

    cursorHighlight = new SelectionLayer(1, 1);
    cursorHighlight.select(0, 0);
    cursorHighlight.setVisible(prefs.getBoolean("cursorhighlight", true));

    mapEventAdapter = new MapEventAdapter();

    // Create the actions
    saveAction = new SaveAction(this);
    saveAsAction = new SaveAsAction(this);
    saveAsImageAction = new SaveAsImageAction(this);
    exitAction = new ExitAction(this, saveAction);
    zoomInAction = new ZoomInAction();
    zoomOutAction = new ZoomOutAction();
    zoomNormalAction = new ZoomNormalAction();
    rot90Action = new LayerTransformAction(MapLayer.ROTATE_90);
    rot180Action = new LayerTransformAction(MapLayer.ROTATE_180);
    rot270Action = new LayerTransformAction(MapLayer.ROTATE_270);
    flipHorAction = new LayerTransformAction(MapLayer.MIRROR_HORIZONTAL);
    flipVerAction = new LayerTransformAction(MapLayer.MIRROR_VERTICAL);
    selectAllAction = new SelectAllAction();
    cancelSelectionAction = new CancelSelectionAction();
    inverseAction = new InverseSelectionAction();
    addLayerAction = new AddLayerAction(this);
    cloneLayerAction = new CloneLayerAction(this);
    deleteLayerAction = new DeleteLayerAction(this);
    moveLayerUpAction = new MoveLayerUpAction(this);
    moveLayerDownAction = new MoveLayerDownAction(this);
    mergeLayerDownAction = new MergeLayerDownAction(this);
    mergeAllLayersAction = new MergeAllLayersAction(this);
    // addObjectGroupAction = new AddObjectGroupAction(this);

    // Create our frame
    appFrame = new JPanel(new BorderLayout());
    appFrame.add(createContentPane(), BorderLayout.CENTER);
    createMenuBar();
    appFrame.add(menuBar, BorderLayout.NORTH);

    setCurrentMap(null);
    updateRecent(null);

    // tileInstancePropertiesDialog = new
    // TileInstancePropertiesDialog(this);

    // Restore the state of the main frame. This needs to happen after
    // making the frame visible, otherwise it has no effect (in Linux).
    // final int state = mainDialogPrefs.getInt("state", Frame.NORMAL);
    // if (state != Frame.ICONIFIED) {
    // appFrame.setExtendedState(state);
    // }

    // Restore the size and position of the layers and tileset panels.
    layersPanel.restore();
    tilesetsPanel.restore();

    // Make sure the map view is redrawn when grid preferences change.
    // todo: move this functionality out of here somehow, but not back into
    // MapView
    final Preferences display = prefs.node("display");
    display.addPreferenceChangeListener(new PreferenceChangeListener() {
      @Override
      public void preferenceChange(PreferenceChangeEvent event) {
        if (mapView == null) {
          return;
        }

        String key = event.getKey();
        if ("gridOpacity".equals(key)) {
          mapView.setGridOpacity(display.getInt("gridOpacity", 255));
        } else if ("gridAntialias".equals(key)) {
          mapView.setAntialiasGrid(display.getBoolean("gridAntialias", true));
        } else if ("gridColor".equals(key)) {
          mapView.setGridColor(new Color(display.getInt("gridColor", MapView.DEFAULT_GRID_COLOR.getRGB())));
        } else if ("showGrid".equals(key)) {
          mapView.setShowGrid(display.getBoolean("showGrid", false));
        }
      }
    });
  }

  /**
   * This method is called, when the frame will be closed
   *
   * @return return true, if the tab can close or false, if it not possible
   */
  @Override
  public boolean closeTab() {
    exitAction.actionPerformed(null);
    return true;
  }

  private JPanel createContentPane() {
    mapScrollPane = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
    mapScrollPane.setBorder(null);

    createData();
    createStatusBar();

    JPanel mapcenter = new JPanel(new BorderLayout());

    // todo: Make continuouslayout an option. Because it can be slow, some
    // todo: people may prefer not to have that.
    layersPanel = new FloatablePanel(dataPanel, PANEL_LAYERS, "layers");

    tabbedTilesetsPane = new TabbedTilesetsPane(this);
    tilesetsPanel = new FloatablePanel(tabbedTilesetsPane, PANEL_TILE_PALETTE, "tilesets");

    // build map center
    mapcenter.add(createToolBar(), BorderLayout.NORTH);
    mapcenter.add(mapScrollPane, BorderLayout.CENTER);

    mainPanel = new JPanel(new BorderLayout());
    mainPanel.add(YFactory.createHorizontPanel(YFactory.createVerticalPanel(layersPanel, tilesetsPanel, "mapeditor", 200), mapcenter,
        "mapeditor", 32 * 9), BorderLayout.CENTER);
    mainPanel.add(statusBar, BorderLayout.SOUTH);

    return mainPanel;
  }

  /**
   * Creates all the menus and submenus of the top menu bar. Handles assigning
   * listeners and tooltips as well.
   */
  private void createMenuBar() {
    JMenuItem save = new TMenuItem(saveAction);
    JMenuItem saveAs = new TMenuItem(saveAsAction);
    JMenuItem saveAsImage = new TMenuItem(saveAsImageAction);
    // JMenuItem close = new TMenuItem(new CloseMapAction(this,
    // saveAction));

    recentMenu = new JMenu(Resources.getString("menu.file.recent"));

    mapEventAdapter.addListener(save);
    mapEventAdapter.addListener(saveAs);
    mapEventAdapter.addListener(saveAsImage);
    // mapEventAdapter.addListener(close);

    JMenu fileMenu = new JMenu(Resources.getString("menu.file"));
    // fileMenu.add(new TMenuItem(new NewMapAction(this, saveAction)));
    // fileMenu.add(new TMenuItem(new OpenMapAction(this, saveAction)));
    // fileMenu.add(recentMenu);
    fileMenu.add(save);
    fileMenu.add(saveAs);
    fileMenu.add(saveAsImage);
    fileMenu.addSeparator();
    // fileMenu.add(close);
    fileMenu.add(new TMenuItem(exitAction));

    JMenuItem copyMenuItem = new TMenuItem(new CopyAction());
    JMenuItem copyAllMenuItem = new TMenuItem(new CopyAllAction());
    JMenuItem cutMenuItem = new TMenuItem(new CutAction());
    JMenuItem pasteMenuItem = new TMenuItem(new PasteAction());
    copyMenuItem.setEnabled(false);
    copyAllMenuItem.setEnabled(false);
    cutMenuItem.setEnabled(false);
    pasteMenuItem.setEnabled(false);

    JMenu transformSub = new JMenu(Resources.getString("menu.edit.transform"));
    transformSub.add(new TMenuItem(rot90Action, true));
    transformSub.add(new TMenuItem(rot180Action, true));
    transformSub.add(new TMenuItem(rot270Action, true));
    transformSub.addSeparator();
    transformSub.add(new TMenuItem(flipHorAction, true));
    transformSub.add(new TMenuItem(flipVerAction, true));
    mapEventAdapter.addListener(transformSub);

    JMenu editMenu = new JMenu(Resources.getString("menu.edit"));
    editMenu.add(new TMenuItem(undoHandler.getUndoAction()));
    editMenu.add(new TMenuItem(undoHandler.getRedoAction()));
    editMenu.addSeparator();
    editMenu.add(copyMenuItem);
    editMenu.add(copyAllMenuItem);
    editMenu.add(cutMenuItem);
    editMenu.add(pasteMenuItem);
    editMenu.addSeparator();
    editMenu.add(transformSub);
    editMenu.addSeparator();
    editMenu.add(createMenuItem(Resources.getString("menu.edit.preferences"), null,
        Resources.getString("menu.edit.preferences.tooltip"), null));
    editMenu.add(createMenuItem(Resources.getString("menu.edit.brush"), null, Resources.getString("menu.edit.brush.tooltip"),
        "control B"));

    mapEventAdapter.addListener(copyMenuItem);
    mapEventAdapter.addListener(copyAllMenuItem);
    mapEventAdapter.addListener(cutMenuItem);
    mapEventAdapter.addListener(pasteMenuItem);

    JMenu mapMenu = new JMenu(Resources.getString("menu.map"));
    mapMenu.add(createMenuItem(Resources.getString("menu.map.resize"), null, Resources.getString("menu.map.resize.tooltip")));
    mapMenu.add(createMenuItem(Resources.getString("menu.map.search"), null, Resources.getString("menu.map.search.tooltip")));
    // mapMenu.addSeparator();
    // mapMenu.add(createMenuItem(Resources.getString("menu.map.properties"),
    // null, Resources.getString("menu.map.properties.tooltip")));
    mapEventAdapter.addListener(mapMenu);

    JMenu layerMenu = new JMenu(Resources.getString("menu.layer"));
    JMenuItem layerAdd = new TMenuItem(addLayerAction);
    mapEventAdapter.addListener(layerAdd);
    layerMenu.add(layerAdd);
    layerMenu.add(new TMenuItem(cloneLayerAction));
    layerMenu.add(new TMenuItem(deleteLayerAction));
    // layerMenu.addSeparator();
    // layerMenu.add(new TMenuItem(addObjectGroupAction));
    layerMenu.addSeparator();
    layerMenu.add(new TMenuItem(moveLayerUpAction));
    layerMenu.add(new TMenuItem(moveLayerDownAction));
    layerMenu.addSeparator();
    layerMenu.add(new TMenuItem(mergeLayerDownAction));
    layerMenu.add(new TMenuItem(mergeAllLayersAction));
    layerMenu.addSeparator();
    layerMenu.add(createMenuItem(Resources.getString("menu.layer.properties"), null,
        Resources.getString("menu.layer.properties.tooltip")));

    JMenu tilesetMenu = new JMenu(Resources.getString("menu.tilesets"));
    // tilesetMenu.add(createMenuItem(
    // Resources.getString("menu.tilesets.new"), null,
    // Resources.getString("menu.tilesets.new.tooltip")));
    // tilesetMenu.add(createMenuItem(
    // Resources.getString("menu.tilesets.import"), null,
    // Resources.getString("menu.tilesets.import.tooltip")));
    // tilesetMenu.addSeparator();
    tilesetMenu.add(createMenuItem(Resources.getString("menu.tilesets.refresh"), null,
        Resources.getString("menu.tilesets.refresh.tooltip"), "F5"));
    // tilesetMenu.addSeparator();
    // tilesetMenu.add(createMenuItem(Resources.getString("menu.tilesets.manager"),
    // null,
    // Resources.getString("menu.tilesets.manager.tooltip")));

    /*
     * objectMenu = new JMenu("Objects");
     * objectMenu.add(createMenuItem("Add Object", null, "Add an object"));
     * mapEventAdapter.addListener(objectMenu);
     *
     * JMenu modifySub = new JMenu("Modify");
     * modifySub.add(createMenuItem("Expand Selection", null, ""));
     * modifySub.add(createMenuItem("Contract Selection", null, ""));
     */

    JMenu selectMenu = new JMenu(Resources.getString("menu.select"));
    selectMenu.add(new TMenuItem(selectAllAction, true));
    selectMenu.add(new TMenuItem(cancelSelectionAction, true));
    selectMenu.add(new TMenuItem(inverseAction, true));
    // selectMenu.addSeparator();
    // selectMenu.add(modifySub);

    gridMenuItem = new JCheckBoxMenuItem(Resources.getString("menu.view.grid"));
    gridMenuItem.addActionListener(this);
    gridMenuItem.setToolTipText(Resources.getString("menu.view.grid.tooltip"));
    gridMenuItem.setAccelerator(KeyStroke.getKeyStroke("control G"));

    cursorMenuItem = new JCheckBoxMenuItem(Resources.getString("menu.view.cursor"));
    cursorMenuItem.setSelected(prefs.getBoolean("cursorhighlight", true));
    cursorMenuItem.addActionListener(this);
    cursorMenuItem.setToolTipText(Resources.getString("menu.view.cursor.tooltip"));

    boundaryMenuItem = new JCheckBoxMenuItem(Resources.getString("menu.view.boundaries"));
    boundaryMenuItem.addActionListener(this);
    boundaryMenuItem.setToolTipText(Resources.getString("menu.view.boundaries.tooltip"));
    boundaryMenuItem.setAccelerator(KeyStroke.getKeyStroke("control E"));

    coordinatesMenuItem = new JCheckBoxMenuItem(Resources.getString("menu.view.coordinates"));
    coordinatesMenuItem.addActionListener(this);
    coordinatesMenuItem.setToolTipText(Resources.getString("menu.view.coordinates.tooltip"));

    JMenu viewMenu = new JMenu(Resources.getString("menu.view"));
    viewMenu.add(new TMenuItem(zoomInAction));
    viewMenu.add(new TMenuItem(zoomOutAction));
    viewMenu.add(new TMenuItem(zoomNormalAction));
    viewMenu.addSeparator();
    viewMenu.add(gridMenuItem);
    viewMenu.add(cursorMenuItem);
    // TODO: Enable when boudary drawing code finished.
    // viewMenu.add(boundaryMenuItem);
    viewMenu.add(coordinatesMenuItem);

    mapEventAdapter.addListener(layerMenu);
    mapEventAdapter.addListener(tilesetMenu);
    mapEventAdapter.addListener(selectMenu);
    mapEventAdapter.addListener(viewMenu);

    JMenu helpMenu = new JMenu(Resources.getString("menu.help"));
    helpMenu.add(createMenuItem(Resources.getString("menu.help.plugins"), null, Resources.getString("menu.help.plugins.tooltip")));
    helpMenu.add(createMenuItem(Resources.getString("menu.help.about"), null, Resources.getString("menu.help.about.tooltip")));

    menuBar = new JMenuBar();
    menuBar.add(fileMenu);
    menuBar.add(editMenu);
    menuBar.add(selectMenu);
    menuBar.add(viewMenu);
    menuBar.add(mapMenu);
    menuBar.add(layerMenu);
    menuBar.add(tilesetMenu);
    // menuBar.add(objectMenu);
    menuBar.add(helpMenu);
  }

  /**
   * Creates the tool bar.
   *
   * @return the created tool bar
   */
  private YToolBar createToolBar() {
    Icon iconMove = Resources.getIcon("gimp-tool-move-22.png");
    Icon iconPaint = Resources.getIcon("gimp-tool-pencil-22.png");
    Icon iconErase = Resources.getIcon("gimp-tool-eraser-22.png");
    Icon iconPour = Resources.getIcon("gimp-tool-bucket-fill-22.png");
    Icon iconEyed = Resources.getIcon("gimp-tool-color-picker-22.png");
    Icon iconMarquee = Resources.getIcon("gimp-tool-rect-select-22.png");
    Icon iconAddObject = Resources.getIcon("gnome-list-add-22.png");
    Icon iconRemoveObject = Resources.getIcon("gnome-list-remove-22.png");
    Icon iconMoveObject = Resources.getIcon("gimp-tool-object-move-22.png");

    paintButton = createToggleButton(iconPaint, "paint", TOOL_PAINT);
    eraseButton = createToggleButton(iconErase, "erase", TOOL_ERASE);
    pourButton = createToggleButton(iconPour, "pour", TOOL_FILL);
    eyedButton = createToggleButton(iconEyed, "eyed", TOOL_EYE_DROPPER);
    marqueeButton = createToggleButton(iconMarquee, "marquee", TOOL_SELECT);
    moveButton = createToggleButton(iconMove, "move", TOOL_MOVE_LAYER);
    objectAddButton = createToggleButton(iconAddObject, "addobject", TOOL_ADD_OBJECT);
    objectRemoveButton = createToggleButton(iconRemoveObject, "removeobject", TOOL_REMOVE_OBJECT);
    objectMoveButton = createToggleButton(iconMoveObject, "moveobject", TOOL_MOVE_OBJECT);

    mapEventAdapter.addListener(moveButton);
    mapEventAdapter.addListener(paintButton);
    mapEventAdapter.addListener(eraseButton);
    mapEventAdapter.addListener(pourButton);
    mapEventAdapter.addListener(eyedButton);
    mapEventAdapter.addListener(marqueeButton);
    mapEventAdapter.addListener(objectMoveButton);

    YToolBar toolBar = new YToolBar();
    // toolBar.setFloatable(true);
    toolBar.add(moveButton);
    toolBar.add(paintButton);
    toolBar.add(eraseButton);
    toolBar.add(pourButton);
    toolBar.add(eyedButton);
    toolBar.add(marqueeButton);
    // toolBar.add(Box.createRigidArea(new Dimension(5, 5)));
    // toolBar.add(objectAddButton);
    // toolBar.add(objectRemoveButton);
    // toolBar.add(objectMoveButton);
    toolBar.add(Box.createRigidArea(new Dimension(0, 5)));
    toolBar.add(new TButton(zoomInAction));
    toolBar.add(new TButton(zoomOutAction));
    toolBar.add(Box.createRigidArea(new Dimension(5, 5)));
    toolBar.add(Box.createGlue());

    brushPreview = new BrushPreview();
    mapEventAdapter.addListener(brushPreview);
    toolBar.add(brushPreview);

    return toolBar;
  }

  private void createData() {
    dataPanel = new JPanel(new BorderLayout());

    // navigation and tool options
    // TODO: the minimap is prohibitively slow, need to speed this up
    // before it can be used
    miniMap = new MiniMapViewer();
    // miniMap.setMainPanel(mapScrollPane);
    JScrollPane miniMapSp = new JScrollPane();
    miniMapSp.getViewport().setView(miniMap);
    miniMapSp.setMinimumSize(new Dimension(0, 120));

    // Layer table
    layerTable = new JTable(new LayerTableModel());
    layerTable.getColumnModel().getColumn(0).setPreferredWidth(32);
    layerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    layerTable.getSelectionModel().addListSelectionListener(this);

    // Opacity slider
    opacitySlider = new JSlider(0, 100, 100);
    opacitySlider.addChangeListener(this);
    JLabel opacityLabel = new JLabel(Resources.getString("dialog.main.opacity.label"));
    opacityLabel.setLabelFor(opacitySlider);

    JPanel sliderPanel = new JPanel();
    sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.X_AXIS));
    sliderPanel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3));
    sliderPanel.add(opacityLabel);
    sliderPanel.add(opacitySlider);
    sliderPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, sliderPanel.getPreferredSize().height));

    // Layer buttons
    AbstractButton layerAddButton = new TButton(addLayerAction);
    mapEventAdapter.addListener(layerAddButton);

    JPanel layerButtons = new JPanel();
    layerButtons.setLayout(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();
    c.fill = GridBagConstraints.BOTH;
    c.weightx = 1;
    layerButtons.add(layerAddButton, c);
    layerButtons.add(new TButton(moveLayerUpAction), c);
    layerButtons.add(new TButton(moveLayerDownAction), c);
    layerButtons.add(new TButton(cloneLayerAction), c);
    layerButtons.add(new TButton(deleteLayerAction), c);
    layerButtons.setMaximumSize(new Dimension(Integer.MAX_VALUE, layerButtons.getPreferredSize().height));

    // tileInstancePropertiesButton = new JButton("Properties");
    // tileInstancePropertiesButton.setActionCommand("tileInstanceProperties");
    // mapEventAdapter.addListener(tileInstancePropertiesButton);
    // tileInstancePropertiesButton.addActionListener(this);

    // Edit history
    /*
     * JScrollPane editSp = new JScrollPane(); editHistoryList = new
     * JList(); editSp.setHorizontalScrollBarPolicy(
     * JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
     * editSp.getViewport().setView(editHistoryList);
     */

    JPanel layerPanel = new JPanel();
    layerPanel.setLayout(new GridBagLayout());
    layerPanel.setPreferredSize(new Dimension(120, 120));
    c = new GridBagConstraints();
    c.insets = new Insets(3, 0, 0, 0);
    c.weightx = 1;
    c.weighty = 0;
    c.fill = GridBagConstraints.BOTH;
    c.gridx = 0;
    c.gridy = 0;
    // layerPanel.add(miniMapSp, c);
    c.weighty = 0;
    c.gridy += 1;
    layerPanel.add(sliderPanel, c);
    c.weighty = 1;
    c.gridy += 1;
    layerPanel.add(new JScrollPane(layerTable), c);
    c.weighty = 0;
    c.insets = new Insets(0, 0, 0, 0);
    c.gridy += 1;
    layerPanel.add(layerButtons, c);
    // c.gridy += 1;
    // layerPanel.add(tileInstancePropertiesButton, c);
    /*
     * c.weighty = 0.25; c.insets = new Insets(3, 0, 0, 0); c.gridy += 1;
     * layerPanel.add(editSp, c);
     */

    // Create paint panel
    TilePalettePanel tilePalettePanel = new TilePalettePanel();

    JPanel brushesPanel = new JPanel();

    JTabbedPane paintPanel = new JTabbedPane();
    paintPanel.add("Palette", tilePalettePanel);
    paintPanel.add("Brushes", brushesPanel);
    paintPanel.setSelectedIndex(1);

    JToolBar tabsPanel = new JToolBar();
    tabsPanel.add(paintPanel);

    dataPanel.add(layerPanel);
  }

  private void createStatusBar() {
    statusBar = new JPanel();
    statusBar.setLayout(new BoxLayout(statusBar, BoxLayout.X_AXIS));

    zoomLabel = new JLabel("100%");
    zoomLabel.setPreferredSize(zoomLabel.getPreferredSize());
    tileCoordsLabel = new JLabel(" ", SwingConstants.CENTER);

    statusBar.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
    JPanel largePart = new JPanel();

    statusBar.add(largePart);
    statusBar.add(tileCoordsLabel);
    statusBar.add(Box.createRigidArea(new Dimension(20, 0)));
    statusBar.add(zoomLabel);
  }

  private void updateLayerTable() {
    int cl = currentLayer;
    if (layerTable.isEditing()) {
      layerTable.getCellEditor(layerTable.getEditingRow(), layerTable.getEditingColumn()).cancelCellEditing();
    }
    ((LayerTableModel) layerTable.getModel()).setMap(currentMap);

    if (currentMap != null) {
      if (currentMap.getTotalLayers() > 0 && cl == -1) {
        cl = 0;
      }

      setCurrentLayer(cl);
    }

    updateLayerOperations();
  }

  private void updateLayerOperations() {
    int nrLayers = 0;

    if (currentMap != null) {
      nrLayers = currentMap.getTotalLayers();
    }

    final boolean validSelection = currentLayer >= 0;
    final boolean notBottom = currentLayer > 0;
    final boolean notTop = currentLayer < nrLayers - 1 && validSelection;
    final boolean tileLayer = validSelection && getCurrentLayer() instanceof TileLayer;
    final boolean objectGroup = validSelection && getCurrentLayer() instanceof ObjectGroup;

    paintButton.setEnabled(tileLayer);
    eraseButton.setEnabled(tileLayer);
    pourButton.setEnabled(tileLayer);
    eyedButton.setEnabled(tileLayer);
    marqueeButton.setEnabled(tileLayer);
    moveButton.setEnabled(validSelection);
    objectAddButton.setEnabled(objectGroup);
    objectRemoveButton.setEnabled(objectGroup);
    objectMoveButton.setEnabled(objectGroup);

    cloneLayerAction.setEnabled(validSelection);
    deleteLayerAction.setEnabled(validSelection);
    moveLayerUpAction.setEnabled(notTop);
    moveLayerDownAction.setEnabled(notBottom);
    mergeLayerDownAction.setEnabled(notBottom);
    mergeAllLayersAction.setEnabled(nrLayers > 1);

    opacitySlider.setEnabled(validSelection);
  }

  private JMenuItem createMenuItem(String name, Icon icon, String tt) {
    JMenuItem menuItem = new JMenuItem(name);
    menuItem.addActionListener(this);
    if (icon != null) {
      menuItem.setIcon(icon);
    }
    if (tt != null) {
      menuItem.setToolTipText(tt);
    }
    return menuItem;
  }

  private JMenuItem createMenuItem(String name, Icon icon, String tt, String keyStroke) {
    JMenuItem menuItem = createMenuItem(name, icon, tt);
    menuItem.setAccelerator(KeyStroke.getKeyStroke(keyStroke));
    return menuItem;
  }

  private AbstractButton createToggleButton(Icon icon, String command, String tt) {
    AbstractButton button;
    button = new JToggleButton("", icon);
    button.setMargin(new Insets(0, 0, 0, 0));
    button.setActionCommand(command);
    button.addActionListener(this);
    if (tt != null) {
      button.setToolTipText(tt);
    }
    return button;
  }

  /**
   * Returns the currently selected tile.
   *
   * @return the currently selected tile
   */
  public Tile getCurrentTile() {
    return currentTile;
  }

  /**
   * Returns the current map.
   *
   * @return the currently selected map
   * @see #setCurrentMap(Map)
   */
  public Map getCurrentMap() {
    return currentMap;
  }

  /**
   * Returns the currently selected layer.
   *
   * @return the currently selected layer
   */
  public MapLayer getCurrentLayer() {
    return currentMap.getLayer(currentLayer);
  }

  /**
   * Returns the currently selected layer index.
   *
   * @return the currently selected layer index
   */
  public int getCurrentLayerIndex() {
    return currentLayer;
  }

  /**
   * Returns the {@link UndoableEditSupport} instance.
   *
   * @return the undo support
   */
  public UndoableEditSupport getUndoSupport() {
    return undoSupport;
  }

  /**
   * Returns the {@link UndoHandler} instance.
   *
   * @return the undo stack
   */
  public UndoHandler getUndoHandler() {
    return undoHandler;
  }

  /**
   * Returns the main application frame.
   *
   * @return the frame of the main application
   */
  public JPanel getAppFrame() {
    return appFrame;
  }

  private void doMouse(MouseEvent event) {
    if (currentMap == null || currentLayer < 0) {
      return;
    }

    Point tile = mapView.screenToTileCoords(event.getX(), event.getY());
    MapLayer layer = getCurrentLayer();

    if (layer == null) {
      return;
    } else if (mouseButton == MouseEvent.BUTTON3) {
      if (layer instanceof TileLayer) {
        if (!bMouseIsDragging) {
          // Click event is sent before the drag event
          // so this one always happens
          Tile newTile = ((TileLayer) layer).getTileAt(tile.x, tile.y);
          setCurrentTile(newTile);
        } else if (currentPointerState == PS_PAINT) {
          // In case we are dragging to create a custom brush, let
          // the user know where we are creating it from
          if (marqueeSelection == null) {
            marqueeSelection = new SelectionLayer(currentMap.getWidth(), currentMap.getHeight());
            currentMap.addLayerSpecial(marqueeSelection);
          }

          Point limp = mouseInitialPressLocation;
          Rectangle oldArea = marqueeSelection.getSelectedAreaBounds();
          int minx = Math.min(limp.x, tile.x);
          int miny = Math.min(limp.y, tile.y);

          Rectangle selRect = new Rectangle(minx, miny, Math.max(limp.x, tile.x) - minx + 1, Math.max(limp.y, tile.y) - miny + 1);

          marqueeSelection.selectRegion(selRect);
          if (oldArea != null) {
            oldArea.add(marqueeSelection.getSelectedAreaBounds());
            mapView.repaintRegion(oldArea);
          }
        }
      } else if (layer instanceof ObjectGroup && !bMouseIsDragging) {
        // Get the object on this location and display the relative
        // options dialog
        ObjectGroup group = (ObjectGroup) layer;
        Point pos = mapView.screenToPixelCoords(event.getX(), event.getY());
        MapObject obj = group.getObjectNear(pos.x, pos.y, mapView.getZoom());
        if (obj != null) {
          ObjectDialog od = new ObjectDialog(YaFrame.get(), obj, undoSupport);
          od.getProps();
        }
      }
    } else if (mouseButton == MouseEvent.BUTTON2 || mouseButton == MouseEvent.BUTTON1
        && (event.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0) {
      // Scroll with middle mouse button
      int dx = event.getX() - mouseInitialScreenLocation.x;
      int dy = event.getY() - mouseInitialScreenLocation.y;
      JViewport mapViewPort = mapScrollPane.getViewport();
      Point currentPosition = mapViewPort.getViewPosition();
      mouseInitialScreenLocation = new Point(event.getX() - dx, event.getY() - dy);

      Point newPosition = new Point(currentPosition.x - dx, currentPosition.y - dy);

      // Take into account map boundaries in order to prevent
      // scrolling past them
      int maxX = mapView.getWidth() - mapViewPort.getWidth();
      int maxY = mapView.getHeight() - mapViewPort.getHeight();
      newPosition.x = Math.min(maxX, Math.max(0, newPosition.x));
      newPosition.y = Math.min(maxY, Math.max(0, newPosition.y));

      mapViewPort.setViewPosition(newPosition);
    } else if (mouseButton == MouseEvent.BUTTON1) {
      switch (currentPointerState) {
      case PS_PAINT:
        paintEdit.setPresentationName(TOOL_PAINT);
        if (layer instanceof TileLayer) {
          try {
            mapView.repaintRegion(currentBrush.doPaint(tile.x, tile.y));
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        break;
      case PS_ERASE:
        paintEdit.setPresentationName(TOOL_ERASE);
        if (layer instanceof TileLayer) {
          ((TileLayer) layer).setTileAt(tile.x, tile.y, null);
          mapView.repaintRegion(new Rectangle(tile.x, tile.y, 1, 1));
        }
        break;
      case PS_POUR:
        paintEdit = null;
        if (layer instanceof TileLayer) {
          TileLayer tileLayer = (TileLayer) layer;
          Tile oldTile = tileLayer.getTileAt(tile.x, tile.y);
          pour(tileLayer, tile.x, tile.y, currentTile, oldTile);
          mapView.repaint();
        }
        break;
      case PS_EYED:
        if (layer instanceof TileLayer) {
          TileLayer tileLayer = (TileLayer) layer;
          Tile newTile = tileLayer.getTileAt(tile.x, tile.y);
          setCurrentTile(newTile);
        }
        break;
      case PS_MOVE: {
        Point translation = new Point(tile.x - mousePressLocation.x, tile.y - mousePressLocation.y);

        layer.translate(translation.x, translation.y);
        moveDist.translate(translation.x, translation.y);
        mapView.repaint();
        break;
      }
      case PS_MARQUEE:
        if (!(layer instanceof TileLayer)) {
          break;
        }
        if (marqueeSelection != null) {
          Point limp = mouseInitialPressLocation;
          Rectangle oldArea = marqueeSelection.getSelectedAreaBounds();
          int minx = Math.min(limp.x, tile.x);
          int miny = Math.min(limp.y, tile.y);

          Rectangle selRect = new Rectangle(minx, miny, Math.max(limp.x, tile.x) - minx + 1, Math.max(limp.y, tile.y) - miny + 1);

          if (event.isShiftDown()) {
            marqueeSelection.add(new Area(selRect));
          } else if (event.isControlDown()) {
            marqueeSelection.subtract(new Area(selRect));
          } else {
            marqueeSelection.selectRegion(selRect);
          }
          if (oldArea != null) {
            oldArea.add(marqueeSelection.getSelectedAreaBounds());
            mapView.repaintRegion(oldArea);
          }
        }
        break;
      case PS_ADDOBJ:
        if (layer instanceof ObjectGroup) {
          if (marqueeSelection == null) {
            marqueeSelection = new SelectionLayer(currentMap.getWidth(), currentMap.getHeight());
            currentMap.addLayerSpecial(marqueeSelection);
          }

          Point limp = mouseInitialPressLocation;
          Rectangle oldArea = marqueeSelection.getSelectedAreaBounds();
          int minx = Math.min(limp.x, tile.x);
          int miny = Math.min(limp.y, tile.y);

          Rectangle selRect = new Rectangle(minx, miny, Math.max(limp.x, tile.x) - minx + 1, Math.max(limp.y, tile.y) - miny + 1);

          marqueeSelection.selectRegion(selRect);
          if (oldArea != null) {
            oldArea.add(marqueeSelection.getSelectedAreaBounds());
            mapView.repaintRegion(oldArea);
          }
        }
        break;
      case PS_REMOVEOBJ:
        if (layer instanceof ObjectGroup) {
          ObjectGroup group = (ObjectGroup) layer;
          Point pos = mapView.screenToPixelCoords(event.getX(), event.getY());
          MapObject obj = group.getObjectNear(pos.x, pos.y, mapView.getZoom());
          if (obj != null) {
            undoSupport.postEdit(new RemoveObjectEdit(group, obj));
            group.removeObject(obj);
            // TODO: repaint only affected area
            mapView.repaint();
          }
        }
        break;
      case PS_MOVEOBJ:
        if (layer instanceof ObjectGroup) {
          Point pos = mapView.screenToPixelCoords(event.getX(), event.getY());
          if (currentObject == null) {
            ObjectGroup group = (ObjectGroup) layer;
            currentObject = group.getObjectNear(pos.x, pos.y, mapView.getZoom());
            if (currentObject == null) { // No object to move
              break;
            }
            mouseLastPixelLocation = pos;
            moveDist = new Point(0, 0);
            break;
          }
          Point translation = new Point(pos.x - mouseLastPixelLocation.x, pos.y - mouseLastPixelLocation.y);
          currentObject.translate(translation.x, translation.y);
          moveDist.translate(translation.x, translation.y);
          mouseLastPixelLocation = pos;
          mapView.repaint();
        }
        break;
      }
    }
  }

  @Override
  public void mouseExited(MouseEvent e) {
    tileCoordsLabel.setText(" ");
  }

  @Override
  public void mouseClicked(MouseEvent e) {}

  @Override
  public void mouseEntered(MouseEvent e) {}

  @Override
  public void mousePressed(MouseEvent e) {
    Point tile = mapView.screenToTileCoords(e.getX(), e.getY());
    mouseButton = e.getButton();
    bMouseIsDown = true;
    bMouseIsDragging = false;
    mousePressLocation = mapView.screenToTileCoords(e.getX(), e.getY());
    mouseInitialPressLocation = mousePressLocation;

    MapLayer layer = getCurrentLayer();

    if (mouseButton == MouseEvent.BUTTON2 || mouseButton == MouseEvent.BUTTON1 && (e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0) {
      // Remember screen location for scrolling with middle mouse button
      mouseInitialScreenLocation = new Point(e.getX(), e.getY());
    } else if (mouseButton == MouseEvent.BUTTON1) {
      switch (currentPointerState) {
      case PS_PAINT:
        if (layer instanceof TileLayer) {
          currentBrush.startPaint(currentMap, tile.x, tile.y, mouseButton, currentLayer);
        }
      case PS_ERASE:
      case PS_POUR:
        paintEdit = new MapLayerEdit(layer, createLayerCopy(layer), null);
        break;
      default:
      }
    }

    if (currentPointerState == PS_MARQUEE) {
      boolean contains = false;
      if (marqueeSelection != null && marqueeSelection.getSelectedArea().contains(tile.x, tile.y)) {
        contains = true;
      }
      if (marqueeSelection == null && !contains) {
        marqueeSelection = new SelectionLayer(currentMap.getWidth(), currentMap.getHeight());
        currentMap.addLayerSpecial(marqueeSelection);
      } else if (marqueeSelection != null && e.getModifiers() == InputEvent.BUTTON1_MASK) {
        currentMap.removeLayerSpecial(marqueeSelection);
        if (contains) {
          marqueeSelection = null;
        } else {
          marqueeSelection = new SelectionLayer(currentMap.getWidth(), currentMap.getHeight());
          currentMap.addLayerSpecial(marqueeSelection);
        }
      }
    } else if (currentPointerState == PS_MOVE) {
      // Initialize move distance to (0, 0)
      moveDist = new Point(0, 0);
    }

    doMouse(e);
  }

  @Override
  public void mouseReleased(MouseEvent event) {
    final MapLayer layer = getCurrentLayer();
    final Point limp = mouseInitialPressLocation;

    if (currentPointerState == PS_MARQUEE) {
      // Uncommented to allow single tile selections
      /*
       * Point tile = mapView.screenToTileCoords(event.getX(),
       * event.getY()); if (tile.y - limp.y == 0 && tile.x - limp.x == 0)
       * { if (marqueeSelection != null) {
       * currentMap.removeLayerSpecial(marqueeSelection); marqueeSelection
       * = null; } }
       */

      // There should be a proper notification mechanism for this...
      // if (marqueeSelection != null) {
      // tileInstancePropertiesDialog.setSelection(marqueeSelection);
      // }
    } else if (currentPointerState == PS_MOVE) {
      if (layer != null && (moveDist.x != 0 || moveDist.x != 0)) {
        undoSupport.postEdit(new MoveLayerEdit(layer, moveDist));
      }
    } else if (currentPointerState == PS_PAINT) {
      if (layer instanceof TileLayer) {
        currentBrush.endPaint();
      }
    } else if (currentPointerState == PS_MOVEOBJ) {
      if (layer instanceof ObjectGroup && currentObject != null && (moveDist.x != 0 || moveDist.x != 0)) {
        undoSupport.postEdit(new MoveObjectEdit(currentObject, moveDist));
      }
    }

    if (/* bMouseIsDragging && */currentPointerState == PS_PAINT || currentPointerState == PS_ADDOBJ) {
      Point tile = mapView.screenToTileCoords(event.getX(), event.getY());
      int minx = Math.min(limp.x, tile.x);
      int miny = Math.min(limp.y, tile.y);

      Rectangle bounds = new Rectangle(minx, miny, Math.max(limp.x, tile.x) - minx + 1, Math.max(limp.y, tile.y) - miny + 1);

      // STAMP
      if (mouseButton == MouseEvent.BUTTON3 && layer instanceof TileLayer) {
        // Right mouse button dragged: create and set custom brush
        TileLayer brushLayer = new TileLayer(bounds);
        brushLayer.copyFrom(getCurrentLayer());
        brushLayer.setOffset(tile.x - (int) bounds.getWidth() / 2, tile.y - (int) bounds.getHeight() / 2);

        // Do a quick check to make sure the selection is not empty
        if (brushLayer.isEmpty()) {
          JOptionPane.showMessageDialog(appFrame, Resources.getString("dialog.selection.empty"),
              Resources.getString("dialog.selection.empty"), JOptionPane.WARNING_MESSAGE);
        } else {
          setBrush(new CustomBrush(brushLayer));
          cursorHighlight.setOffset(tile.x - (int) bounds.getWidth() / 2, tile.y - (int) bounds.getHeight() / 2);
        }
      } else if (mouseButton == MouseEvent.BUTTON1 && layer instanceof ObjectGroup) {
        // TODO: Fix this to use pixels in the first place
        // (with optional snap to grid)
        int w = currentMap.getTileWidth();
        int h = currentMap.getTileHeight();
        MapObject object = new MapObject(bounds.x * w, bounds.y * h, bounds.width * w, bounds.height * h);
        /*
         * Point pos = mapView.screenToPixelCoords( event.getX(),
         * event.getY());
         */
        ObjectGroup group = (ObjectGroup) layer;
        undoSupport.postEdit(new AddObjectEdit(group, object));
        group.addObject(object);
        mapView.repaint();
      }

      // get rid of any visible marquee
      if (marqueeSelection != null) {
        currentMap.removeLayerSpecial(marqueeSelection);
        marqueeSelection = null;
      }
    }

    if (paintEdit != null) {
      if (layer != null) {
        try {
          MapLayer endLayer = paintEdit.getStart().createDiff(layer);
          paintEdit.end(endLayer);
          undoSupport.postEdit(paintEdit);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      paintEdit = null;
    }

    currentObject = null;

    mouseButton = MouseEvent.NOBUTTON;
    bMouseIsDown = false;
    bMouseIsDragging = false;
  }

  @Override
  public void mouseMoved(MouseEvent e) {
    // Update state of mouse buttons
    bMouseIsDown = e.getButton() != MouseEvent.NOBUTTON;
    if (bMouseIsDown) {
      doMouse(e);
    }

    Point tile = mapView.screenToTileCoords(e.getX(), e.getY());
    updateTileCoordsLabel(tile);
    updateCursorHighlight(tile);
  }

  @Override
  public void mouseDragged(MouseEvent e) {
    bMouseIsDragging = true;

    doMouse(e);

    mousePressLocation = mapView.screenToTileCoords(e.getX(), e.getY());
    Point tile = mapView.screenToTileCoords(e.getX(), e.getY());

    updateTileCoordsLabel(tile);
    updateCursorHighlight(tile);
  }

  private void updateTileCoordsLabel(Point tile) {
    if (currentMap.inBounds(tile.x, tile.y)) {
      tileCoordsLabel.setText(String.valueOf(tile.x) + ", " + tile.y);
    } else {
      tileCoordsLabel.setText(" ");
    }
  }

  private void updateCursorHighlight(Point tile) {
    if (prefs.getBoolean("cursorhighlight", true)) {
      Rectangle redraw = cursorHighlight.getBounds();
      Rectangle brushRedraw = currentBrush.getBounds();

      brushRedraw.x = tile.x - brushRedraw.width / 2;
      brushRedraw.y = tile.y - brushRedraw.height / 2;

      if (!redraw.equals(brushRedraw)) {
        if (currentBrush instanceof CustomBrush) {
          CustomBrush customBrush = (CustomBrush) currentBrush;
          ListIterator<MapLayer> layers = customBrush.getLayers();
          while (layers.hasNext()) {
            MapLayer layer = layers.next();
            layer.setOffset(brushRedraw.x, brushRedraw.y);
          }
        }
        mapView.repaintRegion(redraw);
        cursorHighlight.setOffset(brushRedraw.x, brushRedraw.y);
        // cursorHighlight.selectRegion(currentBrush.getShape());
        mapView.repaintRegion(brushRedraw);
      }
    }
  }

  @Override
  public void actionPerformed(ActionEvent event) {
    String command = event.getActionCommand();

    if ("paint".equals(command)) {
      setCurrentPointerState(PS_PAINT);
      resetBrush();
    } else if ("erase".equals(command)) {
      setCurrentPointerState(PS_ERASE);
      resetBrush();
    } else if ("point".equals(command)) {
      setCurrentPointerState(PS_POINT);
      resetBrush();
    } else if ("pour".equals(command)) {
      setCurrentPointerState(PS_POUR);
      resetBrush();
    } else if ("eyed".equals(command)) {
      setCurrentPointerState(PS_EYED);
      resetBrush();
    } else if ("marquee".equals(command)) {
      setCurrentPointerState(PS_MARQUEE);
      resetBrush();
    } else if ("move".equals(command)) {
      setCurrentPointerState(PS_MOVE);
      resetBrush();
    } else if ("addobject".equals(command)) {
      setCurrentPointerState(PS_ADDOBJ);
      resetBrush();
    } else if ("removeobject".equals(command)) {
      setCurrentPointerState(PS_REMOVEOBJ);
      resetBrush();
    } else if ("moveobject".equals(command)) {
      setCurrentPointerState(PS_MOVEOBJ);
      resetBrush();
      // } else if ("tileInstanceProperties".equals(command)) {
      // if (currentMap != null) {
      // tileInstancePropertiesDialog.setVisible(true);
      // }
    } else {
      handleEvent(event);
    }
  }

  // TODO: Most if not all of the below should be moved into action objects
  private void handleEvent(ActionEvent event) {
    String command = event.getActionCommand();

    if (command.equals(Resources.getString("menu.edit.brush"))) {
      BrushDialog bd = new BrushDialog(this, YaFrame.get(), currentBrush);
      bd.setVisible(true);
    } else if (command.equals(Resources.getString("menu.tilesets.new"))) {
      if (currentMap != null) {
        NewTilesetDialog dialog = new NewTilesetDialog(YaFrame.get(), currentMap);
        TileSet newSet = dialog.create();
        if (newSet != null) {
          currentMap.addTileset(newSet);
        }
      }
    } else if (command.equals(Resources.getString("menu.tilesets.import"))) {
      if (currentMap != null) {
        JFileChooser chooser = new JFileChooser(currentMap.getFilename());
        MapHelper.addExtension(chooser);

        int ret = chooser.showOpenDialog(appFrame);
        if (ret == JFileChooser.APPROVE_OPTION) {
          String filename = chooser.getSelectedFile().getAbsolutePath();
          try {
            TileSet set = MapHelper.loadTileset(filename);
            currentMap.addTileset(set);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    } else if (command.equals(Resources.getString("menu.tilesets.refresh"))) {
      if (currentMap != null) {
        Vector<TileSet> tilesets = currentMap.getTilesets();
        for (TileSet tileset : tilesets) {
          try {
            tileset.checkUpdate();
          } catch (IOException e) {
            JOptionPane.showMessageDialog(appFrame, e.getLocalizedMessage(), IMPORT_ERROR_MSG, JOptionPane.WARNING_MESSAGE);
          }
        }
        mapView.repaint();
        brushPreview.setBrush(currentBrush);
      }
    } else if (command.equals(Resources.getString("menu.tilesets.manager"))) {
      if (currentMap != null) {
        TilesetManager manager = new TilesetManager(YaFrame.get(), currentMap);
        manager.setVisible(true);
      }
    } else if (command.equals(Resources.getString("menu.map.properties"))) {
      PropertiesDialog pd = new PropertiesDialog(YaFrame.get(), currentMap.getProperties());
      pd.setTitle(Resources.getString("dialog.properties.map.title"));
      pd.getProps();
    } else if (command.equals(Resources.getString("menu.layer.properties"))) {
      MapLayer layer = getCurrentLayer();
      PropertiesDialog lpd = new PropertiesDialog(YaFrame.get(), layer.getProperties());
      lpd.setTitle(layer.getName() + " " + Resources.getString("dialog.properties.title"));
      lpd.getProps();
    } else if (command.equals(Resources.getString("menu.view.boundaries")) || command.equals("Hide Boundaries")) {
      mapView.toggleMode(MapView.PF_BOUNDARYMODE);
    } else if (command.equals(Resources.getString("menu.view.grid"))) {
      // Toggle grid
      Preferences displayPrefs = prefs.node("display");
      boolean showGrid = displayPrefs.getBoolean("showGrid", false);
      displayPrefs.putBoolean("showGrid", !showGrid);
    } else if (command.equals(Resources.getString("menu.view.coordinates"))) {
      // Toggle coordinates
      mapView.toggleMode(MapView.PF_COORDINATES);
    } else if (command.equals(Resources.getString("menu.view.cursor"))) {
      prefs.putBoolean("cursorhighlight", cursorMenuItem.isSelected());
      cursorHighlight.setVisible(cursorMenuItem.isSelected());
    } else if (command.equals(Resources.getString("menu.map.resize"))) {
      ResizeDialog rd = new ResizeDialog(YaFrame.get(), this);
      rd.setVisible(true);
    } else if (command.equals(Resources.getString("menu.map.search"))) {
      SearchDialog sd = new SearchDialog(YaFrame.get(), currentMap);
      sd.setVisible(true);
    } else if (command.equals(Resources.getString("menu.help.about"))) {
      showAboutDialog();
    } else if (command.equals(Resources.getString("menu.edit.preferences"))) {
      ConfigurationDialog dialog = new ConfigurationDialog(YaFrame.get());
      dialog.configure();
    } else {
      System.out.println(event);
    }
  }

  @Override
  public void componentHidden(ComponentEvent event) {}

  @Override
  public void componentMoved(ComponentEvent event) {}

  @Override
  public void componentResized(ComponentEvent event) {
    // This can currently only happen when the map changes size
    String s = String.valueOf((int) (mapView.getZoom() * 100)) + "%";
    zoomLabel.setText(s);

    // Restore the midpoint
    JViewport mapViewPort = mapScrollPane.getViewport();
    Rectangle viewRect = mapViewPort.getViewRect();
    int absMidX = Math.max(0, Math.round(relativeMidX * mapView.getWidth()) - viewRect.width / 2);
    int absMidY = Math.max(0, Math.round(relativeMidY * mapView.getHeight()) - viewRect.height / 2);
    mapViewPort.setViewPosition(new Point(absMidX, absMidY));
  }

  @Override
  public void componentShown(ComponentEvent event) {}

  @Override
  public void mapChanged(MapChangedEvent e) {
    if (e.getMap() == currentMap) {
      mapScrollPane.setViewportView(mapView);
      updateLayerTable();
      mapView.repaint();
    }
  }

  @Override
  public void tilesetAdded(MapChangedEvent e, TileSet tileset) {}

  @Override
  public void tilesetRemoved(MapChangedEvent e, int index) {
    mapView.repaint();
  }

  @Override
  public void tilesetsSwapped(MapChangedEvent e, int index0, int index1) {}

  @Override
  public void valueChanged(ListSelectionEvent e) {
    int selectedRow = layerTable.getSelectedRow();

    // At the moment, this can only be a new layer selection
    if (currentMap != null && selectedRow >= 0) {
      currentLayer = currentMap.getTotalLayers() - selectedRow - 1;

      float opacity = getCurrentLayer().getOpacity();
      opacitySlider.setValue((int) (opacity * 100));
    } else {
      currentLayer = -1;
    }

    updateLayerOperations();
  }

  @Override
  public void stateChanged(ChangeEvent e) {
    JViewport mapViewport = mapScrollPane.getViewport();

    if (e.getSource() == opacitySlider) {
      if (currentMap != null && currentLayer >= 0) {
        MapLayer layer = getCurrentLayer();
        layer.setOpacity(opacitySlider.getValue() / 100.0f);

        /*
         * MapLayerStateEdit mlse = new MapLayerStateEdit(currentMap);
         * mlse.setPresentationName("Opacity Change");
         * undoSupport.postEdit(mlse);
         */
      }
    } else if (e.getSource() == mapViewport && mapView != null) {
      // Store the point in the middle for zooming purposes
      Rectangle viewRect = mapViewport.getViewRect();
      relativeMidX = Math.min(1, (viewRect.x + viewRect.width / 2) / (float) mapView.getWidth());
      relativeMidY = Math.min(1, (viewRect.y + viewRect.height / 2) / (float) mapView.getHeight());
    }
  }

  /**
   * Returns the map view. Currently only used by the {@link UndoHandler} to
   * be able to order a repaint when doing an undo or redo. This should become
   * obsolete when we add automatic repainting based on layer changes.
   *
   * @return the map view.
   */
  public MapView getMapView() {
    return mapView;
  }

  /**
   * Called when the editor is exiting.
   */
  public void shutdown() {
    // Save the extended window state if the window isn't minimized
    // final int extendedState = appFrame.getExtendedState();
    // final Preferences mainDialogPrefs = prefs.node("dialog/main");
    // mainDialogPrefs.putInt("state", extendedState);
    // if (extendedState == Frame.NORMAL) {
    // mainDialogPrefs.putInt("width", appFrame.getWidth());
    // mainDialogPrefs.putInt("height", appFrame.getHeight());
    // }

    // Allow the floatable panels to save their position and size
    layersPanel.save();
    tilesetsPanel.save();
  }

  private void showAboutDialog() {
    if (aboutDialog == null) {
      aboutDialog = new AboutDialog(YaFrame.get());
    }
    aboutDialog.setVisible(true);
  }

  private class LayerTransformAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = 1047556754094317210L;
    private final int transform;

    public LayerTransformAction(int transform) {
      this.transform = transform;
      switch (transform) {
      case MapLayer.ROTATE_90:
        putValue(NAME, Resources.getString("action.layer.transform.rotate90.name"));
        putValue(SHORT_DESCRIPTION, Resources.getString("action.layer.transform.rotate90.tooltip"));
        putValue(SMALL_ICON, Resources.getIcon("gimp-rotate-90-16.png"));
        break;
      case MapLayer.ROTATE_180:
        putValue(NAME, Resources.getString("action.layer.transform.rotate180.name"));
        putValue(SHORT_DESCRIPTION, Resources.getString("action.layer.transform.rotate180.tooltip"));
        putValue(SMALL_ICON, Resources.getIcon("gimp-rotate-180-16.png"));
        break;
      case MapLayer.ROTATE_270:
        putValue(NAME, Resources.getString("action.layer.transform.rotate270.name"));
        putValue(SHORT_DESCRIPTION, Resources.getString("action.layer.transform.rotate270.tooltip"));
        putValue(SMALL_ICON, Resources.getIcon("gimp-rotate-270-16.png"));
        break;
      case MapLayer.MIRROR_VERTICAL:
        putValue(NAME, Resources.getString("action.layer.transform.vertical.name"));
        putValue(SHORT_DESCRIPTION, Resources.getString("action.layer.transform.vertical.tooltip"));
        putValue(SMALL_ICON, Resources.getIcon("gimp-flip-vertical-16.png"));
        break;
      case MapLayer.MIRROR_HORIZONTAL:
        putValue(NAME, Resources.getString("action.layer.transform.horizontal.name"));
        putValue(SHORT_DESCRIPTION, Resources.getString("action.layer.transform.horizontal.tooltip"));
        putValue(SMALL_ICON, Resources.getIcon("gimp-flip-horizontal-16.png"));
        break;
      }
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      MapLayer currentLayer = getCurrentLayer();
      MapLayer layer = currentLayer;
      MapLayerEdit transEdit;
      transEdit = new MapLayerEdit(currentLayer, createLayerCopy(currentLayer));

      if (marqueeSelection != null) {
        if (currentLayer instanceof TileLayer) {
          layer = new TileLayer(marqueeSelection.getSelectedAreaBounds());
        } else if (currentLayer instanceof ObjectGroup) {
          layer = new ObjectGroup(marqueeSelection.getSelectedAreaBounds());
        }
        layer.setMap(currentMap);
        layer.maskedCopyFrom(currentLayer, marqueeSelection.getSelectedArea());
      }

      switch (transform) {
      case MapLayer.ROTATE_90:
      case MapLayer.ROTATE_180:
      case MapLayer.ROTATE_270:
        transEdit.setPresentationName("Rotate");
        layer.rotate(transform);
        // if(marqueeSelection != null)
        // marqueeSelection.rotate(transform);
        break;
      case MapLayer.MIRROR_VERTICAL:
        transEdit.setPresentationName("Vertical Flip");
        layer.mirror(MapLayer.MIRROR_VERTICAL);
        // if(marqueeSelection != null)
        // marqueeSelection.mirror(transform);
        break;
      case MapLayer.MIRROR_HORIZONTAL:
        transEdit.setPresentationName("Horizontal Flip");
        layer.mirror(MapLayer.MIRROR_HORIZONTAL);
        // if(marqueeSelection != null)
        // marqueeSelection.mirror(transform);
        break;
      }

      if (marqueeSelection != null) {
        layer.mergeOnto(currentLayer);
      }

      transEdit.end(createLayerCopy(currentLayer));
      undoSupport.postEdit(transEdit);
      mapView.repaint();
    }
  }

  private class CancelSelectionAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -6217788914300686640L;

    public CancelSelectionAction() {
      super(Resources.getString("action.select.none.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control shift A"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.select.none.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      if (currentMap != null) {
        if (marqueeSelection != null) {
          currentMap.removeLayerSpecial(marqueeSelection);
        }

        marqueeSelection = null;
      }
    }
  }

  private class SelectAllAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -1980981542520629392L;

    public SelectAllAction() {
      super(Resources.getString("action.select.all.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control A"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.select.all.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      if (currentMap != null) {
        if (marqueeSelection != null) {
          currentMap.removeLayerSpecial(marqueeSelection);
        }
        marqueeSelection = new SelectionLayer(currentMap.getWidth(), currentMap.getHeight());
        marqueeSelection.selectRegion(marqueeSelection.getBounds());
        currentMap.addLayerSpecial(marqueeSelection);
      }
    }
  }

  private class InverseSelectionAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -3030827051213056224L;

    public InverseSelectionAction() {
      super(Resources.getString("action.select.invert.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control I"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.select.invert.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      if (marqueeSelection != null) {
        marqueeSelection.invert();
        mapView.repaint();
      }
    }
  }

  private class ZoomInAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -5253002744432344462L;

    public ZoomInAction() {
      super(Resources.getString("action.zoom.in.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control EQUALS"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.zoom.in.tooltip"));
      putValue(SMALL_ICON, Resources.getIcon("gnome-zoom-in.png"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      if (currentMap != null) {
        zoomOutAction.setEnabled(true);
        if (!mapView.zoomIn()) {
          setEnabled(false);
        }
        zoomNormalAction.setEnabled(mapView.getZoomLevel() != MapView.ZOOM_NORMALSIZE);
      }
    }
  }

  private class ZoomOutAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = 4963537857700059134L;

    public ZoomOutAction() {
      super(Resources.getString("action.zoom.out.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control MINUS"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.zoom.out.tooltip"));
      putValue(SMALL_ICON, Resources.getIcon("gnome-zoom-out.png"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      if (currentMap != null) {
        zoomInAction.setEnabled(true);
        if (!mapView.zoomOut()) {
          setEnabled(false);
        }
        zoomNormalAction.setEnabled(mapView.getZoomLevel() != MapView.ZOOM_NORMALSIZE);
      }
    }
  }

  private class ZoomNormalAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = 4808296576226530709L;

    public ZoomNormalAction() {
      super(Resources.getString("action.zoom.normal.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control 0"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.zoom.normal.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      if (currentMap != null) {
        zoomInAction.setEnabled(true);
        zoomOutAction.setEnabled(true);
        setEnabled(false);
        mapView.setZoomLevel(MapView.ZOOM_NORMALSIZE);
      }
    }
  }

  private class CopyAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -7093838522430390018L;

    public CopyAction() {
      super(Resources.getString("action.copy.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control C"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.copy.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      if (currentMap != null && marqueeSelection != null) {
        if (getCurrentLayer() instanceof TileLayer) {
          clipboardLayer = new TileLayer(marqueeSelection.getSelectedAreaBounds());
        } else if (getCurrentLayer() instanceof ObjectGroup) {
          clipboardLayer = new ObjectGroup(marqueeSelection.getSelectedAreaBounds());
        }
        clipboardLayer.maskedCopyFrom(getCurrentLayer(), marqueeSelection.getSelectedArea());
      }
    }
  }

  private class CopyAllAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = 6636539963576518593L;

    public CopyAllAction() {
      super(Resources.getString("action.copyall.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("shift control C"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.copyall.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      // FIXME: only works for TileLayers
      if (currentMap != null && marqueeSelection != null) {
        clipboardLayer = new TileLayer(marqueeSelection.getSelectedAreaBounds());
        ListIterator<?> itr = currentMap.getLayers();
        while (itr.hasNext()) {
          MapLayer layer = (MapLayer) itr.next();
          if (layer instanceof TileLayer) {
            clipboardLayer.maskedMergeOnto(layer, marqueeSelection.getSelectedArea());
          }
        }
      }
    }
  }

  private class CutAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -244183316986816427L;

    public CutAction() {
      super(Resources.getString("action.cut.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control X"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.cut.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      if (currentMap != null && marqueeSelection != null) {
        MapLayer ml = getCurrentLayer();

        if (getCurrentLayer() instanceof TileLayer) {
          clipboardLayer = new TileLayer(marqueeSelection.getSelectedAreaBounds());
        } else if (getCurrentLayer() instanceof ObjectGroup) {
          clipboardLayer = new ObjectGroup(marqueeSelection.getSelectedAreaBounds());
        }
        clipboardLayer.maskedCopyFrom(ml, marqueeSelection.getSelectedArea());

        Rectangle area = marqueeSelection.getSelectedAreaBounds();
        Area mask = marqueeSelection.getSelectedArea();
        if (ml instanceof TileLayer) {
          TileLayer tl = (TileLayer) ml;
          for (int i = area.y; i < area.height + area.y; i++) {
            for (int j = area.x; j < area.width + area.x; j++) {
              if (mask.contains(j, i)) {
                tl.setTileAt(j, i, null);
              }
            }
          }
        }
        mapView.repaintRegion(area);
      }
    }
  }

  private class PasteAction extends AbstractAction {
    /**
     *
     */
    private static final long serialVersionUID = -9094834729794547379L;

    public PasteAction() {
      super(Resources.getString("action.paste.name"));
      putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control V"));
      putValue(SHORT_DESCRIPTION, Resources.getString("action.paste.tooltip"));
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
      if (currentMap != null && clipboardLayer != null) {
        Vector<MapLayer> layersBefore = currentMap.getLayerVector();
        MapLayer ml = createLayerCopy(clipboardLayer);
        ml.setName(Resources.getString("general.layer.layer") + " " + currentMap.getTotalLayers());
        currentMap.addLayer(ml);
        undoSupport.postEdit(new MapLayerStateEdit(currentMap, layersBefore, new Vector<MapLayer>(currentMap.getLayerVector()),
            "Paste Selection"));
      }
    }
  }

  private void pour(TileLayer layer, int x, int y, Tile newTile, Tile oldTile) {
    if (newTile == oldTile || !layer.canEdit()) {
      return;
    }

    Rectangle area;
    TileLayer before = (TileLayer) createLayerCopy(layer);
    TileLayer after;

    // Check that the copy was succesfully created
    if (before == null) {
      return;
    }

    if (marqueeSelection == null) {
      area = new Rectangle(new Point(x, y));
      Stack<Point> stack = new Stack<Point>();

      stack.push(new Point(x, y));
      while (!stack.empty()) {
        // Remove the next tile from the stack
        Point p = stack.pop();

        // If the tile it meets the requirements, set it and push its
        // neighbouring tiles on the stack.
        if (layer.contains(p.x, p.y) && layer.getTileAt(p.x, p.y) == oldTile) {
          layer.setTileAt(p.x, p.y, newTile);
          area.add(p);

          stack.push(new Point(p.x, p.y - 1));
          stack.push(new Point(p.x, p.y + 1));
          stack.push(new Point(p.x + 1, p.y));
          stack.push(new Point(p.x - 1, p.y));
        }
      }
    } else {
      if (marqueeSelection.getSelectedArea().contains(x, y)) {
        area = marqueeSelection.getSelectedAreaBounds();
        for (int i = area.y; i < area.height + area.y; i++) {
          for (int j = area.x; j < area.width + area.x; j++) {
            if (marqueeSelection.getSelectedArea().contains(j, i)) {
              layer.setTileAt(j, i, newTile);
            }
          }
        }
      } else {
        return;
      }
    }

    Rectangle bounds = new Rectangle(area.x, area.y, area.width + 1, area.height + 1);
    after = new TileLayer(bounds);
    after.copyFrom(layer);

    MapLayerEdit mle = new MapLayerEdit(layer, before, after);
    mle.setPresentationName(TOOL_FILL);
    undoSupport.postEdit(mle);
  }

  public AbstractBrush getBrush() {
    return currentBrush;
  }

  public void resetBrush() {
    // FIXME: this is an in-elegant hack, but it gets the user out
    // of custom brush mode
    // (reset the brush if necessary)
    if (currentBrush instanceof CustomBrush) {
      ShapeBrush sb = new ShapeBrush();
      sb.makeQuadBrush(new Rectangle(0, 0, 1, 1));
      sb.setTile(currentTile);
      setBrush(sb);
    }
  }

  public void setBrush(AbstractBrush brush) {
    // Make sure a possible current highlight gets erased from screen
    if (mapView != null && prefs.getBoolean("cursorhighlight", true)) {
      Rectangle redraw = cursorHighlight.getBounds();
      mapView.repaintRegion(redraw);
    }

    currentBrush = brush;

    // Resize and select the region
    Rectangle brushRedraw = currentBrush.getBounds();
    cursorHighlight.resize(brushRedraw.width, brushRedraw.height, 0, 0);
    cursorHighlight.selectRegion(currentBrush.getShape());
    if (mapView != null) {
      mapView.setBrush(currentBrush);
    }
  }

  /**
   * Checks to see if the undo stack is empty
   *
   * @return <code>true</code> if there is an undo history, <code>false</code>
   *         otherwise.
   */
  @Override
  public boolean isModified() {
    return currentMap != null && undoHandler.canUndo() && !undoHandler.isAllSaved();
  }

  /**
   * Loads a map.
   *
   * @param file
   *            filename of map to load
   * @return <code>true</code> if the file was loaded, <code>false</code> if
   *         an error occured
   */
  public boolean loadMap(Map map) {
    // File exist = new File(file);
    // if (!exist.exists()) {
    // JOptionPane.showMessageDialog(appFrame,
    // Resources.getString("general.file.notexists.message"),
    // Resources.getString("dialog.openmap.error.title"),
    // JOptionPane.ERROR_MESSAGE);
    // return false;
    // }

    try {
      if (map != null) {
        setCurrentMap(map);
        // updateRecent(file);
        return true;
      } else {
        JOptionPane.showMessageDialog(appFrame, Resources.getString("general.file.failed"),
            Resources.getString("dialog.openmap.error.title"), JOptionPane.ERROR_MESSAGE);
      }
    } catch (Throwable t) {
      YEx.warn("Error while loading " + map + " from project " + project + ": " + t.getLocalizedMessage()
          + (t.getCause() != null ? "\nCause: " + t.getCause().getLocalizedMessage() : ""), t);
    }

    return false;
  }

  private static MapLayer createLayerCopy(MapLayer layer) {
    try {
      return (MapLayer) layer.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
      return null;
    }
  }

  public void updateRecent(String filename) {
    // If a filename is given, add it to the recent files
    if (filename != null) {
      TiledConfiguration.addToRecentFiles(filename);
    }

    java.util.List<String> files = TiledConfiguration.getRecentFiles();

    recentMenu.removeAll();

    for (String file : files) {
      recentMenu.add(new TMenuItem(new OpenRecentAction(this, saveAction, file)));
    }
  }

  public void setCurrentMap(Map newMap) {
    // Cancel any active selection
    if (marqueeSelection != null && currentMap != null) {
      currentMap.removeLayerSpecial(marqueeSelection);
    }
    marqueeSelection = null;

    currentMap = newMap;
    boolean mapLoaded = currentMap != null;

    // Create a default brush (protect against a bug with custom brushes)
    ShapeBrush sb = new ShapeBrush();
    sb.makeQuadBrush(new Rectangle(0, 0, 1, 1));
    setBrush(sb);

    tabbedTilesetsPane.setMap(currentMap);

    if (!mapLoaded) {
      mapEventAdapter.fireEvent(MapEventAdapter.ME_MAPINACTIVE);
      mapView = null;
      mapScrollPane.setViewportView(Box.createRigidArea(new Dimension(0, 0)));
      setCurrentPointerState(PS_POINT);
      tileCoordsLabel.setPreferredSize(null);
      tileCoordsLabel.setText(" ");
      zoomLabel.setText(" ");
      setCurrentTile(null);
    } else {
      final Preferences display = prefs.node("display");
      mapEventAdapter.fireEvent(MapEventAdapter.ME_MAPACTIVE);
      mapView = MapView.createViewforMap(currentMap);
      mapView.addMouseListener(this);
      mapView.addMouseMotionListener(this);
      mapView.addComponentListener(this);
      mapView.setGridOpacity(display.getInt("gridOpacity", 255));
      mapView.setAntialiasGrid(display.getBoolean("gridAntialias", true));
      mapView.setGridColor(new Color(display.getInt("gridColor", MapView.DEFAULT_GRID_COLOR.getRGB())));
      mapView.setShowGrid(display.getBoolean("showGrid", false));
      JViewport mapViewport = new JViewport();
      mapViewport.setView(mapView);
      mapViewport.addChangeListener(this);
      mapScrollPane.setViewport(mapViewport);
      setCurrentPointerState(PS_PAINT);

      currentMap.addMapChangeListener(this);

      gridMenuItem.setState(mapView.getShowGrid());
      coordinatesMenuItem.setState(mapView.getMode(MapView.PF_COORDINATES));

      tileCoordsLabel.setText(String.valueOf(currentMap.getWidth() - 1) + ", " + (currentMap.getHeight() - 1));
      tileCoordsLabel.setPreferredSize(null);
      Dimension size = tileCoordsLabel.getPreferredSize();
      tileCoordsLabel.setText(" ");
      tileCoordsLabel.setMinimumSize(new Dimension(20, 50));
      tileCoordsLabel.setPreferredSize(size);
      zoomLabel.setText(String.valueOf((int) (mapView.getZoom() * 100)) + "%");

      // Get the first non-null tile from the first tileset containing
      // non-null tiles.
      Vector<TileSet> tilesets = currentMap.getTilesets();
      Tile firstTile = null;
      if (!tilesets.isEmpty()) {
        Iterator<TileSet> it = tilesets.iterator();
        while (it.hasNext() && firstTile == null) {
          firstTile = it.next().getFirstTile();
        }
      }
      setCurrentTile(firstTile);

      currentMap.addLayerSpecial(cursorHighlight);
    }

    zoomInAction.setEnabled(mapLoaded);
    zoomOutAction.setEnabled(mapLoaded);
    zoomNormalAction.setEnabled(mapLoaded && mapView.getZoomLevel() != MapView.ZOOM_NORMALSIZE);

    /*
     * if (miniMap != null && currentMap != null) {
     * miniMap.setView(MapView.createViewforMap(currentMap)); }
     */

    undoHandler.discardAllEdits();
    updateLayerTable();
  }

  public void setCurrentLayer(int index) {
    if (currentMap != null) {
      int totalLayers = currentMap.getTotalLayers();
      if (totalLayers > index && index >= 0) {
        /*
         * if (paintEdit != null) { MapLayer layer = getCurrentLayer();
         * try { MapLayer endLayer =
         * paintEdit.getStart().createDiff(layer); if (endLayer != null)
         * { endLayer.setId(layer.getId());
         * endLayer.setOffset(layer.getBounds().x,layer.getBounds().y);
         * } paintEdit.end(endLayer); undoSupport.postEdit(paintEdit); }
         * catch (Exception e) { e.printStackTrace(); } }
         */
        currentLayer = index;
        layerTable.changeSelection(totalLayers - currentLayer - 1, 0, false, false);
      }
    }
  }

  /**
   * Changes the currently selected tile.
   *
   * @param tile
   *            the new tile to be selected
   */
  public void setCurrentTile(Tile tile) {
    resetBrush();

    if (currentTile != tile) {
      currentTile = tile;
      if (currentBrush instanceof ShapeBrush) {
        ((ShapeBrush) currentBrush).setTile(tile);
      }
      brushPreview.setBrush(currentBrush);
    }
  }

  private void setCurrentPointerState(int state) {
    /*
     * if (currentPointerState == PS_MARQUEE && state != PS_MARQUEE) { //
     * Special logic for selection if (marqueeSelection != null) {
     * currentMap.removeLayerSpecial(marqueeSelection); marqueeSelection =
     * null; } }
     */

    currentPointerState = state;

    // Select the matching button
    paintButton.setSelected(state == PS_PAINT);
    eraseButton.setSelected(state == PS_ERASE);
    pourButton.setSelected(state == PS_POUR);
    eyedButton.setSelected(state == PS_EYED);
    marqueeButton.setSelected(state == PS_MARQUEE);
    moveButton.setSelected(state == PS_MOVE);
    objectAddButton.setSelected(state == PS_ADDOBJ);
    objectRemoveButton.setSelected(state == PS_REMOVEOBJ);
    objectMoveButton.setSelected(state == PS_MOVEOBJ);

    // Set the matching cursor
    if (mapView != null) {
      switch (currentPointerState) {
      case PS_PAINT:
      case PS_ERASE:
      case PS_POINT:
      case PS_POUR:
      case PS_MARQUEE:
        mapView.setCursor(curDefault);
        break;
      case PS_EYED:
        mapView.setCursor(curEyed);
        break;
      }
    }
  }

  @Override
  public String getIcon() {
    return "map";
  }

  @Override
  public String getTitle() {
    return currentMap == null ? "Loading Map" : currentMap.getFile().getName();
  }

  @Override
  public JComponent getContent() {
    return appFrame;
  }
}
TOP

Related Classes of de.yaams.extensions.basemap.tiled.mapeditor.MapEditor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.